package com.tchepannou.limbe.gae.dao;

import com.google.appengine.api.datastore.*;
import com.tchepannou.limbe.model.Model;

import java.io.Serializable;
import java.util.*;
import java.util.logging.Logger;

/**
 * Implementation of {@link List}
 */
class PersistentList<T extends Model>
    implements List<T>
{
    private static final Logger LOG = Logger.getLogger(PersistentList.class.getName());

    //-- Attribute
    private AbstractGaeDao _dao;
    private Query _q;
    private FetchOptions _fo;
    private List<Key> _keys;

    //-- Constructor
    public PersistentList (Query q, AbstractGaeDao dao, FetchOptions fo)
    {
        _dao = dao;
        _q = q;
        _fo = fo;

        DatastoreService ds = DatastoreServiceFactory.getDatastoreService();

        _q.setKeysOnly();
        PreparedQuery pq = ds.prepare(_q);
        List<Entity> entities = pq.asList(_fo);
        //LOG.info("Executing " + pq.toString() + "\n" + entities);
        _keys = new ArrayList<Key>();
        for (Entity entity : entities)
        {
            _keys.add(entity.getKey());
        }
    }


    //-- Private
    private Key toKey (Object obj)
    {
        if (obj instanceof Model)
        {
            Model model = (Model)obj;
            Serializable id = model.getId ();
            return id != null ? KeyFactory.stringToKey(id.toString()) : null;
        }
        return null;
    }

    //-- List overrides
    public void add(int arg0, T arg1)
    {
        throw new UnsupportedOperationException ();
    }


    public boolean add(T index)
    {
        throw new UnsupportedOperationException ();
    }


    public boolean addAll(Collection<? extends T> arg0)
    {
        throw new UnsupportedOperationException ();
    }


    public boolean addAll(int arg0, Collection<? extends T> arg1)
    {
        throw new UnsupportedOperationException ();
    }


    public void clear()
    {
        throw new UnsupportedOperationException ();
    }


    public boolean contains(Object obj)
    {
        Key key = toKey (obj);
        return _keys.contains(key);
    }


    public boolean containsAll(Collection<?> objs)
    {
        for (Object obj : objs)
        {
            if (!contains(obj))
            {
                return false;
            }
        }
        return true;
    }


    @SuppressWarnings("unchecked")
    public T get(int index)
    {
        Key key = _keys.get(index);
        T model = (T)_dao.findByKey(key);
        return model;
    }


    public int indexOf(Object obj)
    {
        Key key = toKey(obj);
        return _keys.indexOf(key);
    }


    public boolean isEmpty()
    {
        return _keys.isEmpty();
    }


    public Iterator<T> iterator()
    {
        return new PersistentIterator<T>(_keys, _dao);
    }


    public int lastIndexOf(Object obj)
    {
        Key key = toKey(obj);
        return _keys.lastIndexOf(key);
    }


    public ListIterator<T> listIterator()
    {
        return new PersistentListIterator<T>(_keys, _dao);
    }


    public ListIterator<T> listIterator(int index)
    {
        return new PersistentListIterator<T>(index, _keys, _dao);
    }


    public T remove(int arg0)
    {
        throw new UnsupportedOperationException ();
    }


    public boolean remove(Object obj)
    {
        throw new UnsupportedOperationException ();
    }


    public boolean removeAll(Collection<?> arg0)
    {
        throw new UnsupportedOperationException ();
    }


    public boolean retainAll(Collection<?> obj)
    {
        throw new UnsupportedOperationException ();
    }


    public T set(int arg0, T arg1)
    {
        throw new UnsupportedOperationException ();
    }


    public int size()
    {
        return _keys.size();
    }

    @SuppressWarnings("unchecked")
    public List<T> subList(int from, int to)
    {
        List<Key> xkeys = _keys.subList(from, to);
        Map<Key, Model> map = _dao.findByKeys(xkeys);
        List<T> models = new ArrayList<T> ();
        for (Key key : xkeys)
        {
            T model = (T)map.get(key);
            models.add (model);
        }
        return models;
    }

    public Object[] toArray()
    {
        int from = 0;
        int to = _keys.size ();
        return subList(from, to).toArray();
    }

    @SuppressWarnings("hiding")
    public <T> T[] toArray(T[] proto)
    {
        int from = 0;
        int to = _keys.size ();
        return subList(from, to).toArray(proto);
    }

}
